Lantern
Lantern is a data pack library that provides forceloaded chunks and slightly adjusts the data pack loading process. Lantern provides a very small API--the bare minimum required to use the forceloaded chunks effectively.
Lantern offers the following features:
- Load hooks that allow your data pack to verify the version of Lantern or other dependencies.
- Various scoreboard objectives used to hold global variables and data pack metadata.
- A forceloaded chunk for each dimension, each containing several useful utility blocks.
- A permanent armor stand entity useful for computational loot tables and other calculations.
- Per-dimension tick hooks required for certain timing-sensitive operations.
This documentation serves as both a guide on the correct usage of Lantern and a reference of its various features. However, it is not a guide to 1.14 commands or the internal workings of data packs; the reader is expected to possess at least an intermediate understanding of these concepts.
Getting Started
To start working with Lantern, download the latest unbundled release and place
it in your world's datapacks
folder. This method is perfect for learning how
Lantern works, but is not suitable for distributing Lantern to end users.
When you depend on Lantern, rather than using the #minecraft:load
tag to
initialize your data packs, you use #lantern.1:resolve
. The function called
by this tag is responsible for checking Lantern's major version (as multiple
versions of Lantern may attempt to load at once).
Here is an example of a data pack that uses Lantern's tag:
// #lantern.1:resolve
{
"values": [
"#example:resolve"
]
}
# example:resolve
execute if score lantern lantern.versions matches 1 run function example:initialize
# example:initialize
scoreboard players set example lantern.versions 1
say example data pack initialized!
The point of this process is to allow data packs to validate that the Lantern
features they were built to expect are indeed supported. As an example,
example:initialize
can safely assume that the forceloaded chunks exist
and have blocks in the locations they should be.
In example:initialize
, you may note that an example
version was set on the
scoreboard. This is good practice even if no other data packs depend on yours,
as it allows functions called from #minecraft:tick
or advancement triggers to
confirm your data pack's successful initialization.
The version resolution process will be revisted on a later page.
Scoreboard Objectives
Lantern adds four scoreboard objectives, shown in the following table. The first two objectives mainly aid version resolution, while the other two are mainly for runtime data storage and calculations.
Objective Name | Purpose | Persistent |
---|---|---|
lantern.versions | Track versions of loaded data packs | No |
lantern.flags | Store basic (boolean) configuration flags | No |
lantern.const | Define constants used in calculations | No |
lantern.global | Store arbitrary data that survives reloads | Yes |
lantern.versions
and lantern.flags
are guaranteed to be available during
version resolution, regardless of whether the expected Lantern version loaded
successfully. However, lantern.global
and lantern.const
must not be relied
on without confirming that lantern lantern.versions
is 1.
lantern.const
is not strictly for constant values; its precise purpose is to
store values that are finalized during or soon after version resolution and do
not require information from previous reloads. This means that it can also be
used for dynamic settings, not just hardcoded values.
Temporary Globals
Fake players prefixed with #
on the lantern.global
objective are considered
temporary, and data packs may reset or overwrite these values without
restriction. This provides an easy way to perform calculations without needing
to fully namespace every involved variable.
execute store result score #result lantern.global run ...
Numeric Constants
Creating a fully namespaced fake player for every numeric constant, no matter
how simple, would quickly prove tedious. Instead, simply name the constant by
its value with a $
prefix, like this:
scoreboard players set $10000 lantern.const 10000
Forceloaded Chunks
For each of the three dimensions in Minecraft, Lantern forceloads the chunk at
-30000000 0 8880
. The following table gives a brief overview of the blocks in
this chunk:
Position | Block | Purpose |
---|---|---|
-30000000 0 8880 | Lectern | Arbitrary data storage |
-30000000 0 8881 | Sign | Text component resolution |
-30000000 0 8882 | Shulker Box | General item manipulation |
-30000000 0 8883 | Shulker Box | Single item manipulation |
-30000000 0 8884 | Dropper | Advanced loot table usage |
Additionally, an armor stand with a UUID of cb-0-0-0-1
is placed in the
Overworld's chunk.
All positions in these chunks with a Y of 0 or 1 are reserved by Lantern for future use. Other data packs may freely use positions with greater Y values, but there is no guarantee that blocks placed in those positions will persist for any period.
The next five pages will expand upon the roles of each of the forceloaded utilities listed above.
Lectern
-30000000 0 8880
Items in Minecraft have a special NBT compound called tag
. This NBT tag can
contain any other NBT tags, even values that Minecraft would never itself read.
Therefore, item NBT is a useful tool for storing long-term data and applying
transformations on NBT meant for another location.
This is why Lantern provides a forceloaded lectern--lecterns do not tick, have
very low serialization overhead, and can store exactly one item. Therefore,
the lectern's Book.tag
is the best option for your arbitrary NBT storage
needs.
All data packs that use Lantern are free to modify the Book.tag
of the lectern
at -30000000 0 8880
, so long as they do not delete or overwrite Book.tag
completely. It is, however, highly recommended that data packs add their
namespace to the NBT path they choose to use. An example
data pack would
modify Book.tag.example
to avoid conflicting with other data packs.
The lectern provided by Lantern cannot resolve text components. Use the sign instead.
Provided Tags
Lantern currently only provides one tag of its own within the lectern.
Book.tag.lantern.Dimension
contains an int
corresponding to the dimension
the lectern is in. This allows data packs to confirm their current dimension
without using an entity's Dimension
tag. The value of this tag is 0
for the
Overworld, -1
for The Nether, and 1
for The End.
Sign
-30000000 0 8881
Text components are a must-have for any data pack that wishes to display styled
or translated text to the end user. Additionally, they are the only way to
display dynamic values such as score
, selector
, and nbt
. However, these
more advanced text components do not work in every case--notably, they fail with
item names, entity names, and container names.
In the few contexts where advanced text components are allowed, they are
immediately translated into simple components. If a data pack places an advanced
text component into one of these contexts, it could then copy the simplified
text component to any other location (so long as it can be reached with
data modify
).
Lantern provides a sign at -30000000 0 8881
so that data packs can use the
Text1
- Text4
tags for advanced text component resolution. Note that the
execution context is completely ignored by signs, so @s
will not work.
Shulker Boxes
-30000000 0 8882
-30000000 0 8883
Shulker boxes have a very special property, making them stand out even from
other containers--the way a shulker box's contents are dropped is controlled by
the loot table, rather than the game's code. With the correct loot table, a
shulker box's contents can be funneled anywhere with the right loot
command,
without interfering with how shulker boxes drop in survival.
Being able to funnel a shulker box's contents is useful primarily because it is
the only reliable way to dynamically modify the player's inventory, ender chest,
and equipped armor. Additionally, it is a great way to drop large quantities of
natural-looking items on the ground at once (with loot spawn
).
Lantern replaces the minecraft:yellow_shulker_box
's loot table, and provides
two different yellow shulker boxes. The shulker box at -30000000 0 8882
is
free for any use, while the shulker box at -3000000000 0 8883
must be used
only for its first slot (Slot:0b
/ container.0
).
To funnel the contents of one of the shulker boxes, you must use loot
's mine
subcommand with a provided tool of minecraft:air{drop_contents:1b}
. For
example, to give yourself the contents of the -30000000 0 8882
shulker box,
use the following command:
loot give @s mine -30000000 0 8882 minecraft:air{drop_contents:1b}
Bow Dropper
-30000000 0 8884
Sometimes, one may wish to evaluate a loot table to obtain a numeric result (for
example, a value based on the current biome, which is otherwise opaque to
commands). The easiest way to do this is setting an NBT tag containing the
desired result on the generated item. However, to avoid having to check NBT, one
can instead generate multiple unstackable items and use execute store
to count
how many were created.
The issue with the unstackable item approach is that one is required to use
loot spawn
, as the result
of loot replace
or loot insert
is limited by
the number of slots available. This results in unnecessary item entities being
spawned, which makes the approach rather difficult to justify.
There is, however, a strange workaround that allows loot insert
to function
just as well as loot spawn
. The way it works is an implementation detail--just
know that if you make a loot table that drops bows and loot insert
into the
block at -30000000 0 8884
, the result
you get will be the number of bows
dropped, even though the dropper has no room for them.
Overall, this bow dropper fills a small niche, but including it in Lantern does not introduce extra overhead, and it is a useful tool for projects that use computational loot tables.
The bow dropper must not be modified unless the modification is done with
loot insert
!
Armor Stand
cb-0-0-0-1
The NBT tags of entities can be useful for certain calculations, so Lantern
provides an armor stand with a UUID of cb-0-0-0-1
for all data packs to use.
Data packs may use cb-0-0-0-1
for whatever they want, so long as they return
the entity to a forceloaded chunk once they are done, and refrain from killing
it.
The HandItems
may be useful for computational loot tables, as loot ... fish
can take the mainhand
or offhand
of the current executor as arguments, and
loot tables have been experimentally suggested as a viable alternative to
command-based NBT checking.
Dimension Ticks
Minecraft assigns each dimension its own portion of every game tick. First the
Overworld is processed, and then The End, and finally The Nether. In each of
these dimension-specific ticks, various events happen that a data pack may wish
to modify. However, data packs alone are limited to the very beginning of a game
tick (#minecraft:tick
or schedule
), and therefore may not be able to
perfectly detect events from a dimension tick before they influence the world.
Back when command blocks were used instead of data packs, this was hardly an issue to consider. Nearly all events happen in the Overworld, where the command blocks were, and command blocks actually run at the perfect time within the dimension-specific ticks. Unfortunately, it is impossible for data packs to replicate this once-ubiquitous timing without relying on a command block.
Therefore, Lantern includes a repeating command block in each forceloaded chunk, so that data packs may simply hook into the tag(s) for the dimensions in which they need to run functions in. The tags available are listed in this table:
Tag | Dimension |
---|---|
#lantern.1:tick_overworld | Overworld |
#lantern.1:tick_the_nether | The Nether |
#lantern.1:tick_the_end | The End |
There is an additional tag--#lantern.1:tick_dimension
. This tag runs each time
one of the above tags runs, making it useful if your data pack has a single
function that needs to run with the dimension-specific timing for every
dimension.
Dimension ticks can be disabled, as they do cause a slight amount of runtime overhead, and serve a rather niche purpose. Read the Distributing Lantern page to learn how to disable this feature.
Version Resolution
For simple data packs, confirming the versions of any dependencies in
#lantern.1:resolve
is sufficient for the data pack to load. However, some
data packs, such as Lantern itself, have more complex loading processes. For
example, a data pack made to be bundled with other data packs should be aware
that multiple versions of the same data pack may be loaded at once. In this
case, only one version of the pack should be allowed to load.
A data pack aware of multiple versions would look like this:
// #lantern.1:resolve
{
"values": [
"#example:enumerate",
"#example:resolve"
]
}
// #example:enumerate
{
"values": [
"example:v1/enumerate"
]
}
// #example:resolve
{
"values": [
"example:v1/resolve"
]
}
# example:v1/enumerate
execute if score lantern lantern.versions matches 1 unless score example lantern.versions matches 1.. run scoreboard players set example lantern.versions 1
# example:v1/resolve
execute if score example lantern.versions matches 1 run function example:v1/initialize
# example:v1/initialize
say example data pack initialized!
In simple cases, this example
data pack functions identically to the example
on the Getting Started page. However, the difference is that it will not
initialize if a conflicting future version is present at the same time. Instead,
the conflicting future version would be the one to initialize.
Resolution Tags
Lantern offers two tags to assist with version resolution. The first is
#lantern.1:resolve
, which suffices for nearly every use. Data packs can check
for dependencies and then initialize themselves, or expose themselves as
dependencies for other data packs to use. However, there are some features that
may be augmented if processing is done after all dependent packs have been
initialized.
It is for these features that Lantern offers the #lantern.1:post_resolve
tag,
which runs immediately after the #lantern.1:resolve
tag. Post-resolution is a
time for library data packs to use values given by their dependent packs to aid
in an extended initialization process; for example, waiting until this point to
compute some constants to store in the lantern.const
objective.
Bundling
When a data pack has dependencies, it can quickly become a burden for the user to manage and install all of them. This is why it can be useful to merge the dependencies with the final data pack ZIP before you distribute your data pack to your users.
Not all data packs are made to support this, and most that claim to support bundling will break if another data pack bundles the dependency. The use of version resolution features as described on the previous page combined with including the version number in the actual folder names, however, can make bundling work gracefully, even when multiple data packs bundle different versions of the same dependency.
To bundle a data pack, simply merge its data
folder with your data pack's
data
folder. There should not be any conflicts, unless your dependency makes
use of the same function tags that you make use of. To resolve these conflicts,
place the entries of your dependency above your own entries in each affected
function tag.
Bundling can be tedious and error-prone when done manually. However, there is not currently a general-purpose tool to bundle compatible data packs. In the near future, Lantern will publish a standard for data packs that can be used to make the version resolution and bundling processes much simpler.
Ordering
Lantern's version resolution process gives the data pack developer full control over dependency management. However, Minecraft itself has no mechanism to ensure that a dependency will load and initialize before your own data pack. This has lead to packs exposing their own tags that run when they successfully load, which fragments the code of your own data pack, especially if you rely on multiple libraries that use this approach.
There is another way to ensure that dependencies load before dependent
packs--placing the entries in #lantern.1:resolve
for the dependency above the
entries for the dependent pack. If this sounds familiar, it is because this is
exactly what is recommended when bundling data packs together.
Guaranteeing load order is technically possible without bundling, however it requires you to provide your own, empty function tags for every dependency you have. Keep this method in mind, because although bundling is great for many practical use cases, it does not make sense for every data pack.
Note that the ordering only works in the way described above. You cannot reliably execute before or in the middle of a dependency's tags, however you can force your data pack's execution to occur afterwards!
Distributing Lantern
At this point, you should have a fairly good understanding of what Lantern offers, as well as an understanding of the version resolution process. Now you may be wondering how to distribute Lantern alongside your data pack.
For many data packs, bundling is a recommendation. It's good practice, and makes it easier for end users. However, for Lantern, bundling is a requirement of distribution. This is because of the version resolution process--if your data pack goes through version resolution, and then Lantern is unloaded, your data pack may very likely be in an invalid state. The scoreboards suggest that Lantern or any other dependencies were initialized, but this is only because they were not cleared on reload.
To package your data pack for release, take Lantern's data
folder from the
master
branch of the GitHub repository, merge it with your own data
folder,
and handle any function tag conflicts as described on the bundling page.
Configuring Dimension Tags
The dimension tags offered by Lantern are an optional feature, although they are enabled by default. To opt out of dimension tags, delete the following file when you bundle Lantern:
data/lantern.1/functions/flags/enable_dimension_tags.mcfunction
Configuring Forceloading
Although Lantern's primary purpose is to provide forceloaded utility blocks, not every data pack requires access to these. They may, however, wish to use Lantern's version resolution tags. In this case, a data pack may opt out of forceloading by deleting the following file when bundling Lantern:
data/lantern.1/functions/flags/enable_forceloading.mcfunction
Compatibility
This page is a reference for what is required for a data pack to be considered compatible with Lantern. This is a useful reference for external data packs
Location
In each dimension, the cuboid from -30000000 0 8880
to -29999985 1 8895
is
reserved for Lantern internal use. Modifying blocks in this area is strictly
forbidden, except when specifically allowed in this documentation.
Additionally, data packs that depend on Lantern are encouraged to use the cuboid
from -30000000 2 8880
to -29999985 255 8895
for any purpose, and should not
impose additional restrictions on these positions.
Therefore, a library that wishes to provide its own forceloaded utilities should
use its own chunk, rather than the chunk at -30000000 0 8880
.
Entity + UUID
The UUID cb-0-0-0-1
is reserved for an armor stand provided by Lantern. Data
packs must not kill the armor stand or not leave it outside of a Lantern
forceloaded chunk. Additionally, summoning another entity with the same UUID is
expressly forbidden.
Loot Table
The minecraft:yellow_shulker_box
loot table is overridden by Lantern. For a
data pack to be compatible with Lantern, it must not make its own changes to
this loot table. However, it may include a verbatim copy of the loot table (for
example, Minecraft Phi and AESTD do this).
A copy of Lantern's minecraft:yellow_shulker_box
loot table is available
here.